package no.uib.hplims.jobs;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import uk.ac.ebi.kraken.model.blast.parameters.ExpectedThreshold;

import no.uib.hplims.models.BlastInfo;
import no.uib.hplims.models.BlastSearch;
import no.uib.hplims.models.MyOrder;
import no.uib.hplims.models.Peptide;
import no.uib.hplims.models.PeptideFactory;
import no.uib.hplims.tools.events.BlastResultFetchedEvent;
import no.uib.hplims.tools.events.BlastResultsFetchedListener;
import no.uib.hplims.tools.events.BlastSearchCompletedEvent;
import no.uib.hplims.tools.events.BlastSearchCompletedListener;
import no.uib.hplims.tools.events.BlastSearchStartedEvent;
import no.uib.hplims.tools.events.BlastSearchStartedListener;
import no.uib.hplims.tools.events.BlastSubmitFinishedEvent;
import no.uib.hplims.tools.events.BlastSubmitFinishedListener;
import no.uib.hplims.tools.events.PeptideCreatedEvent;
import no.uib.hplims.tools.events.PeptideCreatedListener;
import no.uib.hplims.tools.events.PeptideUpdatedEvent;
import no.uib.hplims.tools.events.PeptideUpdatedListener;

public class BlastTool {

	// Create the listener list
	protected javax.swing.event.EventListenerList listenerList = new javax.swing.event.EventListenerList();

	private PeptideFactory pf = null;
	private ExecutorService es = null;

	private HashMap<Long, Integer> totalNumberOfSequencesSubmitted = new HashMap<Long, Integer>();
	private HashMap<Long, Integer> remainingNumberOfSequencesSubmitted = new HashMap<Long, Integer>();

	/**
	 * @return the PeptideFactory
	 */
	public PeptideFactory getPeptideFactory() {
		if (pf == null) {
			pf = new PeptideFactory();
		}
		return pf;
	}

	public void startBlastSearch(List<String> sequences, MyOrder order,
			String species) {
		setProgress(order, sequences.size());

		// Start BLAST on sequences in list.
		submitSequencesForBlastSearch(sequences, order, species);

	}

	public void resubmitForBlast(ArrayList<String> sequences, MyOrder order, String species) {

		ExecutorService es = getEs();

		setProgress(order, sequences.size());

		SubmitBlastSearchJob s = new SubmitBlastSearchJob(sequences, order, species,
				ExpectedThreshold.ONEHUNDRED);
		s.addListener(blastSubmitFinishedListener);

		final Future future = es.submit(s);
		fireBlastSearchStartedEvent(new BlastSearchStartedEvent(this));
	}

	private void submitSequencesForBlastSearch(List<String> sequences,
			MyOrder order, String species, Enum... params) {
		ExecutorService es = getEs();

		SubmitBlastSearchJob s = new SubmitBlastSearchJob(sequences, order,
				species);
		s.addListener(blastSubmitFinishedListener);

		Future f = es.submit(s);
		fireBlastSearchStartedEvent(new BlastSearchStartedEvent(this));
	}

	/**
	 * @return the ExecutorService
	 */
	public ExecutorService getEs() {
		if (es == null) {
			es = Executors.newFixedThreadPool(3);
		}
		return es;
	}

	/**
	 * @param bi
	 *            Info from Blast search
	 * @return The peptide's name
	 */
	public String generatePeptideName(BlastInfo bi) {
		return PeptideFactory.generatePeptideName(bi);
	}

	private BlastSubmitFinishedListener blastSubmitFinishedListener = new BlastSubmitFinishedListener() {

		public void eventOccurred(BlastSubmitFinishedEvent event) {

			System.out.println("BlastSubmitFinishedEvent");
			Blaster blaster = new Blaster();
			blaster.addListener(blastResultsFetchedListener);
			final Future future = es.submit(blaster);

		}
	};

	private BlastResultsFetchedListener blastResultsFetchedListener = new BlastResultsFetchedListener() {

		public void eventOccurred(BlastResultFetchedEvent event) {

			if (event.getBlastMode().equals(BlastSearch.BLAST_SEARCH_NEW)) {
				Peptide p = getPeptideFactory().createPeptide(event.getQuery(),
						event.getResults(), event.getOrder(),
						event.getSpecies());

				updateProgress(p.getBelongsToOrder());

				PeptideCreatedEvent e = new PeptideCreatedEvent(this, p,
						findTotalNumberOfSequences(event.getOrder()),
						findRemainingNumberOfSequences(event.getOrder()));
				System.out.println("Now firing PeptideCreatedEvent");
				firePeptideCreatedEvent(e);
			} else if (event.getBlastMode().equals(
					BlastSearch.BLAST_SEARCH_UPDATE)) {
				ArrayList<Peptide> list = getPeptideFactory().updatePeptide(
						event.getQuery(), event.getResults(), event.getOrder());

				PeptideUpdatedEvent evt = new PeptideUpdatedEvent(this, list);

				firePeptideUpdatedEvent(evt);
				
				updateProgress(event.getOrder());
			}

		}
	};

	// This methods allows classes to register for PeptideCreatedEvents
	public void addListener(PeptideCreatedListener listener) {
		listenerList.add(PeptideCreatedListener.class, listener);
	}

	// This methods allows classes to unregister for PeptideCreatedEvents
	public void removeListener(PeptideCreatedListener listener) {
		listenerList.remove(PeptideCreatedListener.class, listener);
	}

	// This private class is used to fire PeptideCreatedEvents
	void firePeptideCreatedEvent(PeptideCreatedEvent evt) {
		Object[] listeners = listenerList.getListenerList();
		// Each listener occupies two elements - the first is the listener class
		// and the second is the listener instance
		for (int i = 0; i < listeners.length; i += 2) {
			if (listeners[i] == PeptideCreatedListener.class) {
				((PeptideCreatedListener) listeners[i + 1]).eventOccurred(evt);
			}
		}
	}

	// This methods allows classes to register for PeptideCreatedEvents
	public void addListener(PeptideUpdatedListener listener) {
		listenerList.add(PeptideUpdatedListener.class, listener);
	}

	// This methods allows classes to unregister for PeptideCreatedEvents
	public void removeListener(PeptideUpdatedListener listener) {
		listenerList.remove(PeptideUpdatedListener.class, listener);
	}

	// This private class is used to fire PeptideCreatedEvents
	void firePeptideUpdatedEvent(PeptideUpdatedEvent evt) {
		Object[] listeners = listenerList.getListenerList();
		// Each listener occupies two elements - the first is the listener class
		// and the second is the listener instance
		for (int i = 0; i < listeners.length; i += 2) {
			if (listeners[i] == PeptideUpdatedListener.class) {
				((PeptideUpdatedListener) listeners[i + 1]).eventOccurred(evt);
			}
		}
	}

	// This methods allows classes to register for BlastSearchCompletedEvents
	public void addListener(BlastSearchCompletedListener listener) {
		listenerList.add(BlastSearchCompletedListener.class, listener);
	}

	// This methods allows classes to unregister for BlastSearchCompletedEvents
	public void removeListener(BlastSearchCompletedListener listener) {
		listenerList.remove(BlastSearchCompletedListener.class, listener);
	}

	// This private class is used to fire BlastSearchCompletedEvents
	void fireBlastSearchCompletedEvent(BlastSearchCompletedEvent evt) {
		Object[] listeners = listenerList.getListenerList();
		// Each listener occupies two elements - the first is the listener class
		// and the second is the listener instance
		for (int i = 0; i < listeners.length; i += 2) {
			if (listeners[i] == BlastSearchCompletedListener.class) {
				((BlastSearchCompletedListener) listeners[i + 1])
						.eventOccurred(evt);
				System.out.println("BlastSearchCompleted - listener: "
						+ listeners[i + 1]);
			}
		}
	}

	// This methods allows classes to register for BlastSearchStartedEvents
	public void addListener(BlastSearchStartedListener listener) {
		listenerList.add(BlastSearchStartedListener.class, listener);
	}

	// This methods allows classes to unregister for BlastSearchStartedEvents
	public void removeListener(BlastSearchStartedListener listener) {
		listenerList.remove(BlastSearchStartedListener.class, listener);
	}

	// This private class is used to fire BlastSearchStartedEvents
	void fireBlastSearchStartedEvent(BlastSearchStartedEvent evt) {
		System.out.println("BlastSearchStartedEvent fired");
		Object[] listeners = listenerList.getListenerList();
		// Each listener occupies two elements - the first is the listener class
		// and the second is the listener instance
		for (int i = 0; i < listeners.length; i += 2) {
			if (listeners[i] == BlastSearchStartedListener.class) {
				((BlastSearchStartedListener) listeners[i + 1])
						.eventOccurred(evt);
				System.out.println("BlastSearchStarted - listener: "
						+ listeners[i + 1]);
			}
		}
	}

	private void setProgress(MyOrder order, int size) {
		Long key = order.getId();

		if (totalNumberOfSequencesSubmitted.containsKey(key)) {
			int tmp = totalNumberOfSequencesSubmitted.remove(key);
			tmp += size;
			totalNumberOfSequencesSubmitted.put(key, tmp);
		} else {
			totalNumberOfSequencesSubmitted.put(key, size);
		}

		if (remainingNumberOfSequencesSubmitted.containsKey(key)) {
			int tmp = remainingNumberOfSequencesSubmitted.remove(key);
			tmp += size;
			remainingNumberOfSequencesSubmitted.put(key, tmp);
		} else {
			remainingNumberOfSequencesSubmitted.put(key, size);
		}

	}

	public int findRemainingNumberOfSequences(MyOrder order) {
		Long key = order.getId();
		if (remainingNumberOfSequencesSubmitted.containsKey(key)) {
			return remainingNumberOfSequencesSubmitted.get(key);
		}
		return 0;
	}

	public int findTotalNumberOfSequences(MyOrder order) {
		Long key = order.getId();
		if (totalNumberOfSequencesSubmitted.containsKey(key)) {
			return totalNumberOfSequencesSubmitted.get(key);
		}
		return 0;
	}

	private void updateProgress(MyOrder order) {
		Long key = order.getId();
		reduceRemaining(order);

		if (remainingNumberOfSequencesSubmitted.containsKey(key)) {
			int n = remainingNumberOfSequencesSubmitted.get(key);
			System.out.println("n = " + n);
			if (n == 0) {
				remainingNumberOfSequencesSubmitted.remove(key);
				if (totalNumberOfSequencesSubmitted.containsKey(key)) {
					totalNumberOfSequencesSubmitted.remove(key);
				}
				System.out.println("Firing Blast search completed event");
				fireBlastSearchCompletedEvent(new BlastSearchCompletedEvent(
						this));
			}
		}
	}

	private void reduceRemaining(MyOrder order) {
		Long key = order.getId();
		if (remainingNumberOfSequencesSubmitted.containsKey(key)) {
			int n = remainingNumberOfSequencesSubmitted.get(key);
			n--;
			remainingNumberOfSequencesSubmitted.put(key, n);
		}
	}

}
